package ua.com.globallogic.basecamp.krasnyanskiy.primenumbers;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicIntegerArray;

public class PrimeNumber {
    private List<Integer> result;
    private double finalTime;

    public PrimeNumber buildPrimes(int numberOfThreads, final int range) {

        result = new ArrayList<Integer>();
        int sqrt = (int) Math.sqrt(range);
        final AtomicIntegerArray sieve =
                new AtomicIntegerArray(range + 1);

        ExecutorService service =
                Executors.newFixedThreadPool(numberOfThreads);

        long time = System.currentTimeMillis();
        makeSieve(numberOfThreads, range, sieve, sqrt, service);

        result.add(2);
        for (int k = 3; k <= range; k += 2) {
            if (sieve.get(k) == 0) {
                result.add(k);
            }
        }

        time = System.currentTimeMillis() - time;
        finalTime = (double) time / 1000;
        return this;
    }

    private void makeSieve(int threads,
                           final int range,
                           final AtomicIntegerArray array,
                           final int sqrt,
                           final ExecutorService service) {

        for (int i = 3; i <= sqrt; i += 2) {
            final int tmp = i;
            if (threads > 1) {
                service.submit(new Callable<Void>() {
                    public Void call() throws Exception {
                        if (array.get(tmp) == 0) {
                            for (int k = tmp * tmp; k <= range; k += tmp) {
                                array.getAndSet(k, 1);  // it is very expensive operation! TODO: find more appropriate op!
                            }
                        }
                        return null;
                    }
                });
                threads--;
            } else {
                if (array.get(tmp) == 1) continue;
                for (int n = i * i; n <= range; n += i) {
                    array.getAndSet(n, 1);
                }
            }
        }
        service.shutdown();
    }

    public PrimeNumber withTimePrint() {
        System.out.println("Time: " + finalTime + "s.");
        return this;
    }

    public PrimeNumber withPrimesAmountPrint() {
        System.out.println("Number of primes: " + result.size());
        return this;
    }

    public List<Integer> getResultAsList() {
        return result;
    }
}